package com.yichi.manager;

import android.util.Log;

import com.serotonin.modbus4j.BatchRead;
import com.serotonin.modbus4j.BatchResults;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.code.DataType;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.locator.BaseLocator;
import com.serotonin.modbus4j.msg.WriteCoilRequest;
import com.serotonin.modbus4j.msg.WriteCoilResponse;
import com.serotonin.modbus4j.msg.WriteCoilsRequest;
import com.serotonin.modbus4j.msg.WriteCoilsResponse;
import com.serotonin.modbus4j.msg.WriteRegisterRequest;
import com.serotonin.modbus4j.msg.WriteRegisterResponse;
import com.serotonin.modbus4j.msg.WriteRegistersRequest;
import com.serotonin.modbus4j.msg.WriteRegistersResponse;
import com.yichi.global.AppConfig;

import java.util.ArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * modbus tcp管理
 */
public class ModbusTcpManager {
    private static final String TAG = "ModbusService";

    private ModbusMaster master;
    private ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
    private Integer count = 0;
    private Integer time = 10;

    public ModbusTcpManager() {
    }

    public void initTcp() {
        new Thread(()->{
            ModbusFactory factory = new ModbusFactory();
            IpParameters parameters = new IpParameters();
            parameters.setHost(AppConfig.PLC_MODBUS_HOST);
            parameters.setPort(AppConfig.PLC_MODBUS_PORT);
            master = factory.createTcpMaster(parameters, true);
            master.setTimeout(AppConfig.PLC_MODBUS_REQUEST_TIMEOUT);
            master.setRetries(0);
            try {
                master.init();
                time = 10;
                count = 0;
                Log.v(TAG, "与Modbus服务器连接成功");
            } catch (ModbusInitException e) {
                Log.e(TAG, e.getMessage(), e);
            }
        }).start();
    }

    /**
     * 心跳包
     */
    public void heartbeat() {
        // 这里使用了一个定时任务来每分钟检查一次Modbus连接是否正常，如果出现异常则尝试重新初始化连接
        executorService.scheduleWithFixedDelay(() -> {
            try {
                // 这里使用了一个假定的从站ID为1，寄存器地址为0的读取操作作为连接检查
                master.getValue(BaseLocator.holdingRegister(AppConfig.PLC_MODBUS_SLAVE_ID, AppConfig.PLC_MODBUS_HEARTBEAT_OFFSET, AppConfig.PLC_MODBUS_HEARTBEAT_DATATYPE));
            } catch (Exception e) {
                if (count >= 20) {
                    Log.e(TAG, "超过连接次数，退出");
                    return;
                }
                count++;
                time = time + 20;
                Log.e(TAG, "连接检查失败，尝试重新连接");
                initTcp();
            }
        }, 0, time, TimeUnit.SECONDS);
    }

    /**
     * 关闭连接
     */
    public void close() {
        if (master != null) {
            master.destroy();
        }
        executorService.shutdown();
        Log.v(TAG, "Modbus 连接关闭");
    }

    /**
     * 读取[01H Coil Status 0x]类型 开关数据
     *
     * @param slaveId slaveId
     * @param offset  位置
     * @return 读取值
     */
    public Boolean readCoilStatus(int slaveId, int offset) {
        // 01 Coil Status
        BaseLocator<Boolean> loc = BaseLocator.coilStatus(slaveId, offset);
        try {
            return this.master.getValue(loc);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 读取[02H Input Status 1x]类型 开关数据
     *
     * @param slaveId
     * @param offset
     * @return
     * @throws ModbusTransportException
     * @throws ErrorResponseException
     */
    public Boolean readInputStatus(int slaveId, int offset) {
        // 02 Input Status
        BaseLocator<Boolean> loc = BaseLocator.inputStatus(slaveId, offset);
        try {
            return this.master.getValue(loc);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 读取[03H Holding Register类型 2x]模拟量数据
     *
     * @param slaveId  站点
     * @param offset   位置
     * @param dataType 数据类型,来自com.serotonin.modbus4j.code.DataType
     * @return
     */
    public Number readSingleRegister(int slaveId, int offset, int dataType) {
        // 03 Holding Register类型数据读取
        BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, offset, dataType);
        try {
            return master.getValue(locator);
        } catch (ModbusTransportException | ErrorResponseException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 读取[04H Input Registers 3x]类型 模拟量数据
     *
     * @param slaveId  slaveId
     * @param offset   位置
     * @param dataType 数据类型,来自com.serotonin.modbus4j.code.DataType
     * @return 返回结果
     * @throws ModbusTransportException 异常
     * @throws ErrorResponseException   异常
     */
    public static Number readInputRegisters(ModbusMaster master, int slaveId, int offset, int dataType) {
        // 04 Input Registers类型数据读取
        BaseLocator<Number> loc = BaseLocator.inputRegister(slaveId, offset, dataType);
        try {
            return master.getValue(loc);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 批量读取多个寄存器
     *
     * @param slaveId     站点
     * @param startOffset 开始位置
     * @param count       读取数量
     * @return
     */
    public ArrayList<Integer> readMultipleRegisters(int slaveId, int startOffset, int count, int dataType) {
        BatchRead<Integer> batch = new BatchRead<>();
        if (dataType == DataType.TWO_BYTE_INT_UNSIGNED) {
            // 如果数据类型是2字节的，每个整数占用一个寄存器
            for (int i = 0; i < count; i++) {
                batch.addLocator(i, BaseLocator.holdingRegister(slaveId, startOffset + i, dataType));
            }
        } else if (dataType == DataType.FOUR_BYTE_INT_UNSIGNED) {
            // 如果数据类型是4字节的，每个整数占用两个寄存器
            for (int i = 0; i < count; i++) {
                batch.addLocator(2 * i, BaseLocator.holdingRegister(slaveId, startOffset + 2 * i, DataType.TWO_BYTE_INT_UNSIGNED));
                batch.addLocator(2 * i + 1, BaseLocator.holdingRegister(slaveId, startOffset + 2 * i + 1, DataType.TWO_BYTE_INT_UNSIGNED));
            }
        }
        try {
            BatchResults<Integer> results = master.send(batch);
            ArrayList<Integer> integers = new ArrayList<>();
            if (dataType == DataType.TWO_BYTE_INT_UNSIGNED) {
                for (int i = 0; i < count; i++) {
                    int value = (int) results.getValue(i);
                    integers.add(value);
                }
            } else if (dataType == DataType.FOUR_BYTE_INT_UNSIGNED) {
                for (int i = 0; i < count; i++) {
                    int high = (int) results.getValue(2 * i + 1);
                    int low = (int) results.getValue(2 * i);
                    integers.add((high << 16) | (low & 0xFFFF));
                }
            }
            return integers;
        } catch (ModbusTransportException | ErrorResponseException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 批量读取多个寄存器
     *
     * @param slaveId     站点
     * @param startOffset 开始位置
     * @param count       读取数量
     * @return
     */
    public ArrayList<Short> readMultipleRegisters(int slaveId, int startOffset, int count) {
        BatchRead<Short> batch = new BatchRead<>();
        // 如果数据类型是2字节的，每个整数占用一个寄存器
        for (short i = 0; i < count; i++) {
            batch.addLocator(i, BaseLocator.holdingRegister(slaveId, startOffset + i, DataType.TWO_BYTE_INT_UNSIGNED));
        }
        try {
            BatchResults<Short> results = master.send(batch);
            ArrayList<Short> shorts = new ArrayList<>();
            for (short i = 0; i < count; i++) {
                Short value = (Short) results.getValue(i);
                shorts.add(value);
            }
            return shorts;
        } catch (ModbusTransportException | ErrorResponseException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 写单个（线圈）开关量数据
     * 功能码为：05H，开关量输出点Q置位或复位，写入数据到真机的DO类型的寄存器上面，可以读写的布尔类型(0x)
     *
     * @param slaveId     slave的ID
     * @param writeOffset 位置-预访问的地址-地址范围：0-255
     * @param writeValue  值-置位则为1，复位则为0
     * @return 是否写入成功
     */
    public boolean writeCoil(int slaveId, int writeOffset, boolean writeValue) {
        boolean flag = false;
        try {
            // 创建请求
            WriteCoilRequest request = new WriteCoilRequest(slaveId, writeOffset, writeValue);
            // 发送请求并获取响应对象
            WriteCoilResponse response = (WriteCoilResponse) this.master.send(request);
            flag = !response.isException();
        } catch (ModbusTransportException e) {
            e.printStackTrace();
        }
        return flag;
    }

    /***
     *  保持寄存器写单个
     *  功能码为：06H，将数据写入至V存储器， 数据到真机，数据类型是Int,可以读写的数字类型(4x)
     * @param slaveId slaveId
     * @param writeOffset 开始位置
     * @param writeValue 写入的数据
     */
    public boolean writeRegister(int slaveId, int writeOffset, short writeValue) {
        boolean flag = false;
        try {
            // 创建请求对象
            WriteRegisterRequest request = new WriteRegisterRequest(slaveId, writeOffset, writeValue);
            // 发送请求并获取响应对象
            WriteRegisterResponse response = (WriteRegisterResponse) this.master.send(request);
            flag = !response.isException();
        } catch (ModbusTransportException e) {
            e.printStackTrace();
        }
        return flag;
    }

    /**
     * 写多个开关量数据（线圈）
     * 功能码为：0FH，写多个开关量数据（线圈）
     *
     * @param slaveId     slaveId
     * @param startOffset 开始位置
     * @param bdata       写入的数据
     * @return 是否写入成功
     */
    public boolean writeCoils(int slaveId, int startOffset, boolean[] bdata) {
        boolean flag = false;
        try {
            // 创建请求
            WriteCoilsRequest request = new WriteCoilsRequest(slaveId, startOffset, bdata);
            // 发送请求并获取响应对象
            WriteCoilsResponse response = (WriteCoilsResponse) this.master.send(request);
            flag = !response.isException();
        } catch (ModbusTransportException e) {
            e.printStackTrace();
        }
        return flag;
    }

    /**
     * 保持寄存器写入多个模拟量数据
     * 功能码为：10H,将数据写入至多个V存储器，写入数据到真机，数据类型是short[],可以读写的数字类型(4x)
     *
     * @param slaveId     modbus的slaveID
     * @param startOffset 起始位置偏移量值
     * @param sdata       写入的数据
     * @return 返回是否写入成功
     */
    public boolean writeRegisters(int slaveId, int startOffset, short[] sdata) {
        boolean flag = false;
        try {
            // 创建请求对象
            WriteRegistersRequest request = new WriteRegistersRequest(slaveId, startOffset, sdata);
            // 发送请求并获取响应对象
            WriteRegistersResponse response = (WriteRegistersResponse) this.master.send(request);
            flag = !response.isException();
        } catch (ModbusTransportException e) {
            e.printStackTrace();
        }
        return flag;
    }

    /**
     * 写单个寄存器，功能码应该是06H
     * 根据类型写数据（如:写入Float类型的模拟量、Double类型模拟量、整数类型Short、Integer、Long）
     *
     * @param value    写入值
     * @param dataType com.serotonin.modbus4j.code.DataType
     */
    public void writeHoldingRegister(int slaveId, int offset, Number value, int dataType) {
        try {
            // 类型
            BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, offset, dataType);
            this.master.setValue(locator, value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
